Skip to content

Comments

feat: postMessage API#252

Open
artus9033 wants to merge 12 commits intomainfrom
feat/post-message
Open

feat: postMessage API#252
artus9033 wants to merge 12 commits intomainfrom
feat/post-message

Conversation

@artus9033
Copy link
Collaborator

@artus9033 artus9033 commented Feb 24, 2026

Summary

This PR introduces a postMessage API that resembles the web API.

  • Android:
    • postMessage functionality is fully enabled for the New Architecture.
    • Old Architecture implementation for postMessage is a no-op, with a warning log.
    • ReactNativeBrownfieldModule (New Architecture) now includes emitMessageFromNative for native-to-JS communication.
    • Native message listening via addMessageListener and removeMessageListener is introduced.
  • iOS:
    • postMessage is implemented to send JSON strings from native to the React Native JS layer.
    • onMessage provides a mechanism for native code to subscribe to messages sent from JavaScript.
  • JavaScript:
    • ReactNativeBrownfield.postMessage sends JSON-serializable data to native.
    • ReactNativeBrownfield.onMessage subscribes to native-to-JS messages, returning a subscription object.
  • Documentation: introduced postMessage-related API documentation
  • Demo apps:
    • reworked the demo apps to demonstrate postMessage usage
    • reorganized the native consumer apps' files - extracted components to separate files for code quality in the native demos

Test plan

CI green + manual tests

@artus9033 artus9033 self-assigned this Feb 24, 2026
@artus9033 artus9033 marked this pull request as ready for review February 24, 2026 23:01
Copilot AI review requested due to automatic review settings February 24, 2026 23:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a postMessage/onMessage messaging API to @callstack/react-native-brownfield, enabling JSON-based message exchange between the React Native JS layer and native host apps, plus updated docs and demo apps to showcase usage.

Changes:

  • Added JS postMessage and onMessage APIs backed by a TurboModule event emitter (onBrownfieldMessage).
  • Implemented native-side message plumbing on iOS and Android (including native-to-JS emission helpers and JS-to-native dispatch listeners).
  • Updated API reference docs and reworked demo apps (RNApp, ExpoApp, native consumer demos) to demonstrate the messaging flow.

Reviewed changes

Copilot reviewed 44 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
yarn.lock Dependency lockfile updates (Expo-related bumps).
packages/react-native-brownfield/src/index.ts Exposes JS postMessage/onMessage API surface.
packages/react-native-brownfield/src/NativeReactNativeBrownfieldModule.ts Adds postMessage + onBrownfieldMessage event emitter to the TurboModule spec.
packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.swift Adds JS→native message notification posting helper.
packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm Exports native module method and forwards native→JS notifications into the JS event emitter.
packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.h Declares static native helper for emitting messages to JS.
packages/react-native-brownfield/ios/ReactNativeBrownfield.swift Adds host-side native→JS postMessage and JS→native onMessage subscription.
packages/react-native-brownfield/ios/Notification+Brownfield.swift Defines notification names for message exchange.
packages/react-native-brownfield/android/src/oldarch/ReactNativeBrownfieldModule.kt Old-arch JS→native dispatch + no-op native→JS emitter with warning.
packages/react-native-brownfield/android/src/newarch/ReactNativeBrownfieldModule.kt New-arch JS→native dispatch + native→JS event emission.
packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt Adds native message listeners + dispatch + native→JS entrypoint.
docs/docs/index.md Updates homepage feature blurb related to messaging/state sync.
docs/docs/docs/api-reference/react-native-brownfield/swift.mdx Documents Swift postMessage / onMessage.
docs/docs/docs/api-reference/react-native-brownfield/objective-c.mdx Documents ObjC postMessage / onMessage.
docs/docs/docs/api-reference/react-native-brownfield/kotlin.mdx Documents Kotlin postMessage + message listeners.
docs/docs/docs/api-reference/react-native-brownfield/javascript.mdx Documents JS postMessage + onMessage subscription.
docs/docs/docs/api-reference/react-native-brownfield/java.mdx Documents Java postMessage + message listeners.
apps/TesterIntegrated/swift/App.swift Swift demo UI updated to exercise message exchange.
apps/TesterIntegrated/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt Kotlin demo UI updated to exercise message exchange.
apps/RNApp/src/HomeScreen.tsx RN demo updated with chat-like UI around postMessage/onMessage.
apps/RNApp/ios/Podfile.lock Updates iOS demo pods (Brownie/ReactBrownfield versions).
apps/ExpoApp/package.json Bumps Expo dependencies for the Expo demo.
apps/ExpoApp/components/ui/icon-symbol.tsx Adds icon mapping for new tab icon.
apps/ExpoApp/components/postMessage/MessageBubble.tsx Adds message bubble component for Expo demo.
apps/ExpoApp/components/postMessage/Message.ts Adds message model for Expo demo.
apps/ExpoApp/app/(tabs)/postMessage.tsx Adds postMessage demo tab screen for Expo app.
apps/ExpoApp/app/(tabs)/index.tsx Refactors tab index screen layout wrapper.
apps/ExpoApp/app/(tabs)/_layout.tsx Adds a new postMessage tab.
apps/AppleApp/prepareXCFrameworks.js Patches iOS consumer ContentView moduleName/GreetingCard based on app type.
apps/AppleApp/package.json Updates iOS consumer build script configuration name.
apps/AndroidApp/app/src/vanilla/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt Adds app name constant for Android demo.
apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/PostMessageCard.kt Adds Compose card demonstrating postMessage in Android demo.
apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/MaterialCard.kt Adds shared Compose card wrapper component.
apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/components/GreetingCard.kt Extracts greeting UI into its own component.
apps/AndroidApp/app/src/main/java/com/callstack/brownfield/android/example/MainActivity.kt Refactors Android demo screen to use extracted components + postMessage card.
apps/AndroidApp/app/src/expo/java/com/callstack/brownfield/android/example/ReactNativeConstants.kt Adjusts main module name + app name for Expo Android demo variant.
.changeset/fair-zebras-heal.md Declares a minor version bump for the new API.
Comments suppressed due to low confidence (3)

packages/react-native-brownfield/android/src/newarch/ReactNativeBrownfieldModule.kt:31

  • The New Architecture module stores a static sharedInstance reference to the module and never clears it. This can keep the ReactApplicationContext alive longer than intended and can also leave a stale instance after RN teardown/recreation (e.g., fast refresh / host recreation). Consider using a WeakReference and/or clearing sharedInstance in the appropriate lifecycle hook (invalidate/onCatalystInstanceDestroy, depending on the base class).
    apps/TesterIntegrated/swift/App.swift:141
  • This builds JSON by string interpolation: {"text":"\(text)"}. If text contains quotes, backslashes, or newlines, the JSON becomes invalid and parsing on the RN side will fail. Consider building the payload via JSONSerialization/JSONEncoder so strings are properly escaped.
              Button("Send") {
                let text = draft.isEmpty ? "Hello from Swift! (#\(nextId))" : draft
                let json = "{\"text\":\"\(text)\"}"
                ReactNativeBrownfield.shared.postMessage(json)
                withAnimation(.spring(response: 0.35, dampingFraction: 0.7)) {

docs/docs/index.md:22

  • The phrase "message exchange lag" reads like a typo in this context (it describes a feature, but "lag" means delay). If you meant the integration "layer", consider updating the wording to avoid confusion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +13 to +30
static ReactNativeBrownfieldModule *_sharedInstance = nil;

- (instancetype)init {
self = [super init];
if (self) {
_sharedInstance = self;

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNativeToJSMessage:)
name:@"BrownfieldMessageToJSNotification"
object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_sharedInstance is a static raw pointer that is never cleared. If the module instance is deallocated (bridge invalidation / reload), _sharedInstance can become a dangling pointer and emitMessageFromNative: may message a deallocated instance (crash). In dealloc, clear _sharedInstance (ideally only if it equals self) and consider guarding access for multi-instance scenarios.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +17
private const val LOG_TAG = "ReactNativeBrownfieldModule"

fun emitMessageFromNative(text: String) {
Log.w(
LOG_TAG,
"ReactNativeBrownfieldModule::emitMessageFromNative only supports the New Architecture. This call is ineffective and will not cause any messages to be emitted."
)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log.w(...) is used in emitMessageFromNative, but android.util.Log is not imported in this file, which will fail compilation. Add the missing import (or remove logging if not needed).

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +33
postMessage: (data: unknown): void => {
const serialized = JSON.stringify(data);
ReactNativeBrownfieldModule.postMessage(serialized);
},
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postMessage uses JSON.stringify(data) without handling failures. JSON.stringify can throw (e.g., cyclic structures / BigInt) and can also return undefined (e.g., undefined, functions), which would then be passed to the native postMessage(message: string) and likely fail at runtime. Consider wrapping serialization in try/catch and either (a) throw a clear JS-side error or (b) normalize undefined to a valid string (e.g., 'null') before calling the native module.

Copilot uses AI. Check for mistakes.
Copy link
Member

@hurali97 hurali97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 👍 I have a few comments:

  • Maybe instead of bloating ReactNativeBrownfield.shared we can use composition to expose ReactNativeBrownfield.message and expose all the messaging functionality
  • I think having to update RNApp + ExpoApp + TesterIntegrated, looks like added maintenance cost. Maybe we can consider dropping TesterIntegrated from the repo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants